package chain

import (
	"context"
	"crypto/ecdsa"
	"encoding/json"
	"fmt"
	"math/big"
	"time"

	"github.com/simplechain-org/go-simplechain/accounts/keystore"
	"github.com/simplechain-org/go-simplechain/common"
	"github.com/simplechain-org/go-simplechain/core/types"
)

func (c *Client) SendTransactionUpChain(key *keystore.Key, nonce uint64, gasLimit uint64, gasPrice *big.Int, data []byte) (string, error) {
	return c.SendUpChain(key, NormalTransaction, nonce, key.Address, "0", gasLimit, gasPrice, data)
}
func (c *Client) SendUpChain(key *keystore.Key, opType int, nonce uint64, to common.Address, amount string, gasLimit uint64, gasPrice *big.Int, data []byte) (string, error) {
	var rawTx *types.Transaction
	amountBigInt, ok := new(big.Int).SetString(amount, 10)
	if !ok {
		amountBigInt = new(big.Int)
	}
	switch opType {
	case CreateContract:
		rawTx = types.NewContractCreation(nonce, amountBigInt, gasLimit, gasPrice, data)
	case CallContract:
		rawTx = types.NewTransaction(nonce, to, big.NewInt(0), gasLimit, gasPrice, data)
	case NormalTransaction:
		rawTx = types.NewTransaction(nonce, to, amountBigInt, gasLimit, gasPrice, data)
	}
	//todo
	signedTx, err := types.SignTx(rawTx, types.HomesteadSigner{}, key.PrivateKey)
	if err != nil {
		return "", err
	}
	txHash := signedTx.Hash().Hex()
	if err := c.client.SendTransaction(context.TODO(), signedTx); err != nil {
		return txHash, err
	}
	return txHash, nil
}

func (c *Client) SendTransaction(opType int, nonce uint64, to string, amount string, gasLimit uint64, gasPrice *big.Int, data []byte, privateKey *ecdsa.PrivateKey, chainId *big.Int) (*string, error) {
	var rawTx *types.Transaction
	amountBigInt, ok := new(big.Int).SetString(amount, 10)
	if !ok {
		amountBigInt = new(big.Int)
	}
	switch opType {
	case CreateContract:
		rawTx = types.NewContractCreation(nonce, amountBigInt, gasLimit, gasPrice, data)
	case CallContract:
		to := common.HexToAddress(to)
		rawTx = types.NewTransaction(nonce, to, big.NewInt(0), gasLimit, gasPrice, data)
	case NormalTransaction:
		if !common.IsHexAddress(to) {
			return nil, fmt.Errorf("%s is not HexAddress", to)
		}
		to := common.HexToAddress(to)
		rawTx = types.NewTransaction(nonce, to, amountBigInt, gasLimit, gasPrice, data)
	}
	signer := types.NewEIP155Signer(chainId)
	signedTx, err := types.SignTx(rawTx, signer, privateKey)
	if err != nil {
		return nil, err
	}
	if err := c.client.SendTransaction(context.TODO(), signedTx); err != nil {
		return nil, err
	}
	txHash := signedTx.Hash().Hex()
	return &txHash, nil
}
func (c *Client) JudgeupchainstatusFast(txId string, opType string, receiptchan chan types.Receipt, dones chan error) {
	receipt, err := c.JudgeUpChainStatus(txId, opType)
	if err == nil {
		receiptchan <- *receipt
	} else {
		var tmp types.Receipt
		receiptchan <- tmp
	}
	dones <- err
}
func (c *Client) JudgeUpChainStatus(txId string, opType string) (*types.Receipt, error) {
	ticker := time.NewTicker(30 * time.Second)
	meterCount := 0
	for {
		select {
		case <-ticker.C:
			ticker.Stop()
			return nil, fmt.Errorf("get txId:%v opType(%v) timeout(30s)", txId, opType)
		default:
			receipt, err := c.GetTransactionReceipt(txId)
			if err != nil {
				return nil, err
			}
			if receipt == nil {
				if meterCount >= 30 {
					return nil, fmt.Errorf("get txId:%v opType(%v) times(30)", txId, opType)
				}
				time.Sleep(1 * time.Second)
				meterCount++
				continue
			}
			if receipt.Status == 0 {
				res, _ := receipt.Bloom.MarshalText()
				return nil, fmt.Errorf("%v up chain success but transaction is invalid,err:%v", opType, string(res))
			}
			return receipt, nil
		}
	}
}
func (c *Client) GetTransactionReceipt(txId string) (*types.Receipt, error) {
	var r *types.Receipt
	err := c.rpcClient.Call(&r, "eth_getTransactionReceipt", common.HexToHash(txId))
	if err != nil {
		return nil, err
	}
	return r, nil
}
func (c *Client) SendTransactionData(nonce uint64, to string, gasLimit uint64, gasPrice *big.Int, data []byte, privateKey *ecdsa.PrivateKey, chainId *big.Int) (*string, error) {
	return c.SendTransaction(NormalTransaction, nonce, to, "0", gasLimit, gasPrice, data, privateKey, chainId)
}

type Transaction struct {
	AccountNonce     string `json:"nonce"`
	Price            string `json:"gasPrice"`
	GasLimit         string `json:"gas"`
	Recipient        string `json:"to"`
	Amount           string `json:"value"`
	Payload          string `json:"input"`
	TransactionIndex string `json:"transactionIndex"`
	BlockNumber      string `json:"blockNumber"`
	BlockHash        string `json:"blockHash"`
	V                string `json:"v"`
	R                string `json:"r"`
	S                string `json:"s"`
	Hash             string `json:"hash"`
	From             string `json:"from"`
}

func (c *Client) GetTransactionByHash(txId string) (*Transaction, error) {
	var res interface{}
	err := c.rpcClient.Call(&res, "eth_getTransactionByHash", txId)
	if err != nil {
		return nil, err
	}
	transactionInfo, err := json.MarshalIndent(res, "", "	")
	if err != nil {
		return nil, err
	}
	var transaction Transaction
	err = json.Unmarshal(transactionInfo, &transaction)
	if err != nil {
		return nil, err
	}
	err = ExchangeBlockTransaction(&transaction)
	if err != nil {
		return nil, err
	}
	return &transaction, nil
}
func ExchangeBlockTransaction(inputTransaction interface{}) error {
	switch inputTransaction.(type) {
	case *Transaction:
		transaction := inputTransaction.(*Transaction)
		tmp, err := HexStringToDecString(transaction.BlockNumber)
		if err != nil {
			return err
		}
		transaction.BlockNumber = *tmp
		tmp, err = HexStringToDecString(transaction.AccountNonce)
		if err != nil {
			return err
		}
		transaction.AccountNonce = *tmp

		tmp, err = HexStringToDecString(transaction.Amount)
		if err != nil {
			return err
		}
		transaction.Amount = *tmp

		tmp, err = HexStringToDecString(transaction.GasLimit)
		if err != nil {
			return err
		}
		transaction.GasLimit = *tmp

		tmp, err = HexStringToDecString(transaction.Price)
		if err != nil {
			return err
		}
		transaction.Price = *tmp

		tmp, err = HexStringToDecString(transaction.TransactionIndex)
		if err != nil {
			return err
		}
		transaction.TransactionIndex = *tmp
	case *MixTransaction:
		transaction := inputTransaction.(*MixTransaction)
		tmp, err := HexStringToDecString(transaction.Tx.BlockNumber)
		if err != nil {
			return err
		}
		transaction.Tx.BlockNumber = *tmp
		tmp, err = HexStringToDecString(transaction.Tx.AccountNonce)
		if err != nil {
			return err
		}
		transaction.Tx.AccountNonce = *tmp

		tmp, err = HexStringToDecString(transaction.Tx.Amount)
		if err != nil {
			return err
		}
		transaction.Tx.Amount = *tmp

		tmp, err = HexStringToDecString(transaction.Tx.GasLimit)
		if err != nil {
			return err
		}
		transaction.Tx.GasLimit = *tmp

		tmp, err = HexStringToDecString(transaction.Tx.Price)
		if err != nil {
			return err
		}
		transaction.Tx.Price = *tmp

		tmp, err = HexStringToDecString(transaction.Tx.TransactionIndex)
		if err != nil {
			return err
		}
		transaction.Tx.TransactionIndex = *tmp
	}
	return nil
}

func HexStringToDecString(hexString string) (*string, error) {
	v, success := big.NewInt(0).SetString(hexString, 0)
	if !success {
		return nil, fmt.Errorf("invalid input")
	}
	if v.Cmp(big.NewInt(0)) < 0 {
		return nil, fmt.Errorf("hex must be larger than 0")
	}
	decString := v.Text(10)
	return &decString, nil
}

type MixTransaction struct {
	Tx              Transaction `json:"transaction,omitempty"`
	Status          string      `json:"status"`
	ContractAddress string      `json:"contractAddress"`
	Sipc            string      `json:"chain" gencodec:"required"`
}
